package fx.parser;

import java.util.Scanner;
import java.util.regex.Pattern;

public class Parser {
	private static final Pattern LEFT_PARENTHESIS_PATTERN  = Pattern.compile("\\(");
	private static final Pattern RIGHT_PARENTHESIS_PATTERN = Pattern.compile("\\)");
	private static final Pattern AND_PATTERN               = Pattern.compile("&");
	private static final Pattern OR_PATTERN                = Pattern.compile("\\|");
	private static final Pattern EXCLUDE_PATTERN           = Pattern.compile("~");
	private static final Pattern PHRASE_PATTERN            = Pattern.compile("[^()&|~]+");
	private static final Pattern DELIMITER_PATTERN         =
		Pattern.compile("(?<![()&|~])(?=[()&|~])|(?<=[()&|~])");
	
	private final Scanner scanner;
	
	public Parser(final String src) {
		this.scanner = new Scanner(src);
	}
	
	////////////////////////
	// parser methods
	////////////////////////
	
	public BooleanExpression parse() throws GrammarException {
		initializeScanner();
		BooleanExpression expr = parseExcludeExpression();
		matchEnd();
		return expr;
	}
	
	private BooleanExpression parseExcludeExpression() throws GrammarException {
		BooleanExpression expr = parseOrExpression();
		if (nextIsExclude()) {
			matchExclude();
			BooleanExpression right = parseExcludeExpression();
			expr = new ExcludeExpression(expr, right);
		}
		return expr;
	}
	
	private BooleanExpression parseOrExpression() throws GrammarException {
		BooleanExpression expr = parseAndExpression();
		while (nextIsOr()) {
			matchOr();
			BooleanExpression right = parseAndExpression();
			expr = new OrExpression(expr, right);
		}
		return expr;
	}
	
	private BooleanExpression parseAndExpression() throws GrammarException {
		BooleanExpression expr = parsePrimaryExpression();
		while (nextIsAnd()) {
			matchAnd();
			BooleanExpression right = parsePrimaryExpression();
			expr = new AndExpression(expr, right);
		}
		return expr;
	}
	
	private BooleanExpression parsePrimaryExpression() throws GrammarException {
		if (nextIsLeftParenthesis()) {
			matchLeftParenthesis();
			BooleanExpression expr = parseExcludeExpression();
			matchRightParenthesis();
			return expr;
		} else {
			return parsePhraseExpression();
		}
	}
	
	private BooleanExpression parsePhraseExpression() throws GrammarException {
		String phrase = matchPhrase();
		return new PhraseExpression(phrase);
	}
	
	////////////////////////
	// scanner methods
	////////////////////////
	
	private void initializeScanner() {
		this.scanner.reset().useDelimiter(DELIMITER_PATTERN);
	}
	
	// (
	
	private boolean nextIsLeftParenthesis() {
		return this.scanner.hasNext(LEFT_PARENTHESIS_PATTERN);
	}
	
	private void matchLeftParenthesis() throws GrammarException {
		try {
			this.scanner.next(LEFT_PARENTHESIS_PATTERN);
		} catch (Exception e) {
			throw new GrammarException("expecting left parenthesis ('(')", e);
		}
	}
	
	// )
	
	private void matchRightParenthesis() throws GrammarException {
		try {
			this.scanner.next(RIGHT_PARENTHESIS_PATTERN);
		} catch (Exception e) {
			throw new GrammarException("expecting right parenthesis (')')", e);
		}
	}
	
	// &
	
	private boolean nextIsAnd() {
		return this.scanner.hasNext(AND_PATTERN);
	}
	
	private void matchAnd() throws GrammarException {
		try {
			this.scanner.next(AND_PATTERN);
		} catch (Exception e) {
			throw new GrammarException("expecting and operator ('&')", e);
		}
	}
	
	// |
	
	private boolean nextIsOr() {
		return this.scanner.hasNext(OR_PATTERN);
	}
	
	private void matchOr() throws GrammarException {
		try {
			this.scanner.next(OR_PATTERN);
		} catch (Exception e) {
			throw new GrammarException("expecting or operator ('|')", e);
		}
	}
	
	// ~
	
	private boolean nextIsExclude() {
		return this.scanner.hasNext(EXCLUDE_PATTERN);
	}
	
	private void matchExclude() throws GrammarException {
		try {
			this.scanner.next(EXCLUDE_PATTERN);
		} catch (Exception e) {
			throw new GrammarException("expecting exclude operator ('~')", e);
		}
	}
	
	// phrase
	
	private String matchPhrase() throws GrammarException {
		try {
			return this.scanner.next(PHRASE_PATTERN);
		} catch (Exception e) {
			throw new GrammarException("expecting a phrase", e);
		}
	}
	
	private void matchEnd() throws GrammarException {
		if (this.scanner.hasNext()) {
			throw new GrammarException(
				"expecting end of expression, but found: " + this.scanner.next());
		}
	}
}
